package bll

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/panjf2000/ants/v2"
	"gitlab.local/backend/proto/dto"
	"gitlab.local/backend/proto/manager"
	"go-micro.dev/v4/client"
	"net/url"
	"os"
	"ota/client/grpc"
	log "ota/collector/logger"
	"ota/config"
	"ota/errors"
	"ota/event"
	infraOss "ota/lib/infra/oss"
	"ota/lib/trace_log"
	"ota/model"
	"ota/model/entity"
	"ota/store"
	"ota/store/postgres"
	"ota/utils"
	"strings"
	"time"
)

var (
	poolOption = []ants.Option{
		ants.WithExpiryDuration(time.Duration(1800) * time.Second), // 30分钟
		ants.WithPreAlloc(false),
		ants.WithPanicHandler(func(err interface{}) {
			log.Errorf(fmt.Sprintf("exec task panic :%+v", utils.Stack()))
			_, _ = fmt.Fprintln(os.Stderr, err)
		}),
	}
)

type ota struct {
	v2               *upgradeV2
	v1               *upgradeV1
	firmwareCache    *firmwareCacheType
	iUserUpgrade     store.IUserUpgrade
	iFirmware        store.IFirmware
	iUpgradeStrategy store.IUpgradeStrategy
	iUpgradePlan     store.IUpgradePlan
}

var Ota = &ota{
	v1:               &upgradeV1{},
	v2:               &upgradeV2{},
	iUserUpgrade:     postgres.UserUpgrade,
	iFirmware:        postgres.Firmware,
	iUpgradeStrategy: postgres.UpgradeStrategy,
	iUpgradePlan:     postgres.UpgradePlan,
}

// ShadowFirmwareVersionKey 设备影子中 固件版本key
const ShadowFirmwareVersionKey = "firmware_version"

func init() {
	Register(Ota)
}

func (a *ota) init() func() {
	c := config.Conf.OSS
	ossInstance := infraOss.Init(&infraOss.Config{
		StsEndPoint:  c.StsEndPoint,
		EndPoint:     c.EndPoint,
		AccessKey:    c.AccessKey,
		AccessSecret: c.AccessSecret,
		Bucket:       c.Bucket,
		Region:       c.Region,
		Role:         c.Role,
		Expire:       900,
		FrontUrl:     "",
	})
	a.firmwareCache = newFirmwareCache(ossInstance, time.Hour)
	go a.dealPendingUpgradePlan()
	go a.dealUpgradingUpgradePlan()
	return func() {}
}

func (a *ota) onEvent(*event.Data) {}

// 启动检测还没有执行的升级任务
func (a *ota) dealPendingUpgradePlan() {

	var (
		err             error
		errMessage      []string
		infoMessage     []string
		totalCount      int
		respUpgradePlan []*entity.UpgradePlan
		ctx             = context.Background()
	)

	defer func() {
		var (
			logger  = log.Info
			message = infoMessage
			result  = "SUCCESS"
		)
		if errMessage != nil {
			logger = log.Error
			result = "FAILED"
			message = append(message, errMessage...)
		}

		logger(fmt.Sprintf("【 dealPendingUpgradePlan Result:%v 】 pending state's totalcount:%v message:%v",
			result, totalCount, strings.Join(message, " ")))

	}()

	var (
		silenceUpgradePlan []string
		timeoutUpgradePlan []string
		t                  = time.Now().Unix()
	)
	if _, respUpgradePlan, err = a.iUpgradePlan.List(ctx, &model.UpgradePlanListRequest{
		Status: utils.GetValuePointer(int(model.UpgradePlanStatusPending)),
		Index:  1,
		Size:   -1,
	}, model.QueryOptions{OnlyList: true}); err != nil {
		errMessage = append(errMessage, fmt.Errorf("list upgrade plan error:%+v", err).Error())
		return
	}

	totalCount = len(respUpgradePlan)

	for _, v := range respUpgradePlan {
		if _, e := UpgradeStrategy.Find(ctx, &model.UpgradeStrategyInfoRequest{Id: v.StrategyId}); e != nil {
			errMessage = append(errMessage, fmt.Errorf("get upgrade strategy error:%+v according plan id:%v ", err, v.Id).Error())
			continue
		} else {
			if v.Id > 0 {
				if v.StartTime < t {
					timeoutUpgradePlan = append(timeoutUpgradePlan, fmt.Sprintf("%v-%v", v.Id, v.Name))
				} else {
					event.Emit(&event.Data{Type: model.UpgradePlanOpCreate, Data: &model.UpgradePlanCreateRequest{Id: v.Id, StartTime: v.StartTime}})
					silenceUpgradePlan = append(silenceUpgradePlan, fmt.Sprintf("%v-%v", v.Id, v.Name))
				}
			}
		}
	}

	infoMessage = append(infoMessage, fmt.Sprintf("add timer silence upgrade plan list:%v", strings.Join(silenceUpgradePlan, ",")))
	infoMessage = append(infoMessage, fmt.Sprintf("add timer timeout upgrade plan list:%v", strings.Join(timeoutUpgradePlan, ",")))
	return
}

// 启动检测正在升级中的任务
func (a *ota) dealUpgradingUpgradePlan() {

	var (
		ctx               = context.Background()
		planIds           []int64
		pendingRequests   []*model.OtaUpgradeRequest
		upgradingRequests []*model.OtaUpgradeRequest
	)
	if _, respUpgradePlan, err := a.iUpgradePlan.List(ctx, &model.UpgradePlanListRequest{
		Status: utils.GetValuePointer(int(model.UpgradePlanStatusUpgrading)),
		Index:  1,
		Size:   -1,
	}, model.QueryOptions{OnlyList: true}); err != nil {
		log.Errorf("list upgrade plan error:%+v", err)
		return
	} else {
		for _, v := range respUpgradePlan {
			planIds = append(planIds, v.Id)
		}
	}

	var (
		upgradingList []int64
	)

	_, respUserUpgrade, err := a.iUserUpgrade.List(ctx, &model.UserUpgradeListRequest{
		PlanIds:  planIds,
		Statuses: []int{int(model.UpgradeResultPending), int(model.UpgradeResultUpgrading)},
		Index:    1,
		Size:     -1,
	}, model.QueryOptions{OnlyList: true})
	if err != nil {
		log.Errorf("list upgrade plan error:%+v", err)
		return
	}

	for _, v := range respUserUpgrade {

		uReq := &model.OtaUpgradeRequest{
			ID:         v.Id,
			SNCode:     v.EbikeSnCode,
			Status:     utils.GetValuePointer(model.OtaAgree),
			PlanId:     v.PlanId,
			StrategyId: v.StrategyId,
		}

		if model.OtaUpgradeResultType(v.Status) == model.UpgradeResultUpgrading {
			upgradingList = append(upgradingList, v.Id)
			upgradingRequests = append(upgradingRequests, uReq)
			continue
		}

		pendingRequests = append(pendingRequests, uReq)
	}

	if len(upgradingList) == 0 && len(pendingRequests) == 0 {
		log.Info("no unfinished upgrade task")
		return
	}

	if len(pendingRequests) != 0 {

		/*
		   待升级的任务直接提交升级任务
		*/
		err = upPendingTimer.submitUpgradeTask(pendingRequests)
		if err != nil {
			log.Errorf("submit upgrade task err:%v", err)
		}
	}

	if len(upgradingRequests) != 0 {
		/*
		   TODO
		   升级中的任务，先读取设备的当前版本号是否是要升级的版本号，如果是那么表示已经升级成功,否则需要先将任务重置之后再执行升级
		*/

		err = UserUpgrade.Update(ctx, &model.UserUpgradeUpdateRequest{Ids: upgradingList, Status: utils.GetValuePointer(int(model.UpgradeResultPending))})
		if err != nil {
			log.Errorf("【 restart Upgrade 】upgrade plan error:%+v】", err)
			return
		}
		err = upPendingTimer.submitUpgradeTask(upgradingRequests)
		if err != nil {
			log.Errorf("submit upgrade task err:%v", err)
		}
	}

	return
}

func (a *ota) CheckVersion(ctx context.Context, in *model.OtaCheckVersionRequest) (*model.OtaCheckVersionResponse, error) {

	var (
		err                 error
		errMessage          []string
		infoMessage         []string
		respLatestUpgrade   *model.UserUpgradeInfo
		respUpgradePlanInfo *model.UpgradePlanInfo
	)
	defer func() {
		var (
			logger  = log.Info
			message = infoMessage
			result  = "SUCCESS"
		)
		if errMessage != nil {
			logger = log.Error
			result = "FAILED"
			message = append(message, errMessage...)
		}

		logger(fmt.Sprintf("【 Check Version sn_code:%+v Result:%v 】%v",
			in.SNCode, result, strings.Join(message, " ")))

		trace_log.DebugLogger(in.SNCode, "CheckVersion in:", utils.ToJsonString(in), "err:", err)
	}()

	/*
	   首先获取设备是否在线信息
	*/
	if ok := IsOnline(in.SNCode); !ok {
		return nil, fmt.Errorf("device is offline")
	}
	/*
	   如果给同一个设备配置了多个升级计划，那么按照任务的先后顺序新执行先建立的任务
	*/

	if resp, e := UserUpgrade.List(ctx, &model.UserUpgradeListRequest{
		UpgradeTypes: []int{int(model.OtaAutonomous), int(model.OtaMandatory)},
		EbikeSnCode:  utils.GetValuePointer(in.SNCode),
		Status:       utils.GetValuePointer(int(model.UpgradeResultPending)),
		Index:        1,
		Size:         1,
	}, model.QueryOptions{
		OnlyList:    true,
		OrderFields: model.NewOrderFields(model.NewOrderField("created_at", model.OrderByASC)),
	}); e != nil {
		errMsg := fmt.Sprintf("Get version information(UserUpgrade) err %v", e)
		errMessage = append(errMessage, errMsg)
		return nil, errors.InternalServerError.Error()
	} else {
		if resp == nil || len(resp.List) == 0 {
			errMsg := fmt.Sprintf("No version information found err %v", e)
			errMessage = append(errMessage, errMsg)
			return nil, nil
		}
		respLatestUpgrade = resp.List[0]
	}

	allowStatus := map[model.OtaUpgradeResultUpgradingType]error{
		model.OtaUpgradeResultUpgradingNormal:          nil,
		model.OtaUpgradeResultUpgradingQueueing:        nil,
		model.OtaUpgradeResultUpgradingOfflineDelaying: fmt.Errorf("checkversion will not returing task's of offlinie state"),
	}

	if v, ok := allowStatus[model.OtaUpgradeResultUpgradingType(respLatestUpgrade.UpgradingStatus)]; ok && v != nil {
		infoMessage = append(infoMessage, v.Error())
		return nil, nil
	}

	respUpgradePlanInfo, err = UpgradePlan.Find(ctx, &model.UpgradePlanInfoRequest{Id: respLatestUpgrade.PlanId})
	if err != nil {
		errMsg := fmt.Sprintf("Get version information(UpgradeStrategy) err %v", err)
		errMessage = append(errMessage, errMsg)
		return nil, errors.InternalServerError.Error()
	}

	out := &model.OtaCheckVersionResponse{
		UpgradeType:   respUpgradePlanInfo.UpgradeType,
		Status:        respLatestUpgrade.Status,
		EbikeName:     respLatestUpgrade.EbikeName,
		EbikeTypeName: respLatestUpgrade.EbikeTypeName,
		ID:            respLatestUpgrade.Id,
		PlanId:        respLatestUpgrade.PlanId,
		StrategyId:    respLatestUpgrade.UpgradeStrategyID,
		Details:       respLatestUpgrade.Detail,
	}
	return out, err
}

func (a *ota) upgradeCheck(upgradeCache *userUpgradeValueCache, in *model.OtaUpgradeRequest) (model.OtaUpgradeResultType, error) {

	/*
	   1. 强制升级场景，用户只能升级
	   2. 自主升级场景，用户可以取消或者选择升级,如果选择升级,如果升级状态不是待升级状态,那么返回错误
	*/

	var status model.OtaUpgradeResultType
	upgradeType := model.UpgradeType(upgradeCache.UpgradeType)
	if upgradeType == model.OtaMandatory {
		if *in.Status != model.OtaAgree {
			return model.OtaCancel, fmt.Errorf("must be agree while upgrade plan upgrade type is mandatory")
		}

		return model.UpgradeResultUpgrading, nil
	}

	if upgradeType == model.OtaAutonomous || upgradeType == model.OtaSilence {
		if *in.Status == model.OtaAgree {

			var (
				upgradeResultStatus = model.OtaUpgradeResultType(upgradeCache.UserUpgradeStatus)
				allowStatus         = map[model.OtaUpgradeResultType]struct{}{
					model.UpgradeResultPending: {},
					model.UpgradeResultFailure: {},
				}
			)

			if _, ok := allowStatus[upgradeResultStatus]; !ok {
				e := fmt.Errorf("current upgrade result is %v,refuse to upgrade", upgradeResultStatus)
				return status, e
			}

			status = model.UpgradeResultUpgrading
		}

		if *in.Status == model.OtaCancel {
			status = model.UpgradeResultAbolishment
		}

		if *in.Status == model.OtaDelayNotify {
			status = model.UpgradeResultPending
		}

		//if *in.Status == model.OtaDelayNotify {
		//	status = model.UpgradeResultAbolishment
		//}
	}

	return status, nil
}

func (a *ota) Result(ctx context.Context, in *model.OtaResultRequest) (*model.OtaResultResponse, error) {

	if res, ok := userUpgradeStatusCache.Get(in.Id); ok {
		return &model.OtaResultResponse{Status: int(res.(model.OtaUpgradeResultType))}, nil
	}

	info, err := a.iUserUpgrade.Find(ctx, &model.UserUpgradeInfoRequest{
		Id: in.Id,
	})
	if err != nil {
		return nil, err
	}

	return &model.OtaResultResponse{Status: info.Status, StatusDetail: info.StatusDetail, Progress: info.Progress}, nil
}

func (a *ota) Record(ctx context.Context, in *model.OtaRecordRequest) (*model.OtaRecordResponse, error) {

	t, list, err := a.iUserUpgrade.List(ctx, &model.UserUpgradeListRequest{
		Index:       in.Index,
		Size:        in.Size,
		EbikeSnCode: &in.SNCode,
		Statuses:    []int{int(model.UpgradeResultSuccess), int(model.UpgradeResultFailure)},
	}, model.GetQueryOption(model.QueryOptions{OrderFields: model.NewOrderFields(model.NewOrderField("updated_at", model.OrderByDESC))}))
	if err != nil {
		return nil, err
	}

	return &model.OtaRecordResponse{
		Total: int64(t),
		List:  list,
	}, nil
}

func (a *ota) UpgradeNext(ctx context.Context, id int64) (int64, error) {

	var (
		upgradeRequests []*model.OtaUpgradeRequest
		us              = int(model.OtaUpgradeResultUpgradingQueueing)
	)

	_, list, err := a.iUserUpgrade.List(ctx, &model.UserUpgradeListRequest{
		Statuses:        []int{int(model.UpgradeResultPending)},
		UpgradeTypes:    []int{int(model.OtaSilence)},
		UpgradingStatus: &us,
		Index:           1,
		Size:            1,
	}, model.QueryOptions{OnlyList: true, OrderFields: model.NewOrderFields(model.NewOrderField("created_at", model.OrderByASC)), Conditions: []string{fmt.Sprintf("id > %v", id)}})
	if err != nil {
		return 0, fmt.Errorf("list upgrade plan error:%+v", err)
	}

	if len(list) == 0 {
		return 0, fmt.Errorf("no upgrading quequeing task after :%+v", id)
	}

	v := list[0]

	uReq := &model.OtaUpgradeRequest{
		ID:         v.Id,
		SNCode:     v.EbikeSnCode,
		Status:     utils.GetValuePointer(model.OtaAgree),
		PlanId:     v.PlanId,
		StrategyId: v.StrategyId,
	}

	upgradeRequests = append(upgradeRequests, uReq)

	go func() {
		if err := func() error {
			<-time.After(time.Minute * 3)
			err = upPendingTimer.submitUpgradeTask(upgradeRequests)
			if err != nil {

				return fmt.Errorf("submit upgrade task err:%v", err)
			}
			return nil
		}(); err != nil {
			log.Error(err)
		}
	}()

	return v.Id, nil
}

/* 执行离线指令，设备上线之后，由 manager 触发rpc接口调用*/
func OfflineUpgrade(ctx context.Context, in *dto.OfflineUpgradeRequest) error {

	var (
		err               error
		errMessage        []string
		infoMessage       []string
		respLatestUpgrade *model.UserUpgradeInfo
	)
	defer func() {
		var (
			logger  = log.Info
			message = infoMessage
			result  = "SUCCESS"
		)
		if errMessage != nil {
			logger = log.Error
			result = "FAILED"
			message = append(message, errMessage...)
		}

		logger(fmt.Sprintf("【 OfflineUpgrade sn_code:%+v Result:%v 】%v",
			in.Sn, result, strings.Join(message, " ")))

		trace_log.DebugLogger(in.Sn, "OfflineUpgrade in:", utils.ToJsonString(in), "err:", err)
	}()

	resp, e := UserUpgrade.List(ctx, &model.UserUpgradeListRequest{
		EbikeSnCode:     utils.GetValuePointer(in.Sn),
		Status:          utils.GetValuePointer(int(model.UpgradeResultPending)),
		UpgradingStatus: utils.GetValuePointer(int(model.OtaUpgradeResultUpgradingOfflineDelaying)),
		Index:           1,
		Size:            1,
	}, model.QueryOptions{
		OnlyList:    true,
		OrderFields: model.NewOrderFields(model.NewOrderField("created_at", model.OrderByASC)),
	})
	if e != nil {
		err = e
		errMsg := fmt.Sprintf("Get offline upgrade information(OfflineUpgrade) err %v", e)
		errMessage = append(errMessage, errMsg)
		return errors.InternalServerError.Error()
	}
	if resp == nil || len(resp.List) == 0 {
		errMsg := fmt.Errorf("no offline upgrade information found err")
		errMessage = append(errMessage, errMsg.Error())
		return errMsg
	}
	respLatestUpgrade = resp.List[0]

	return Ota.Upgrade(ctx, &model.OtaUpgradeRequest{
		ID:         respLatestUpgrade.Id,
		SNCode:     in.Sn,
		Status:     utils.GetValuePointer(model.OtaAgree),
		PlanId:     respLatestUpgrade.PlanId,
		StrategyId: respLatestUpgrade.UpgradeStrategyID,
	})
}

func (a *ota) upgradeCallBack(
	ftSettings map[string]config.FirmwareTypeSettingsItem,
	in *model.OtaUpgradeRequest, upgradeOrder []model.UpgradePlanDetail,
	fv1CallBack func(ft model.FirmwareTypeDetail, order model.UpgradePlanDetail) error,
	fv2CallBack func(id uint64, vv config.FirmwareTypeSettingsItem, order model.UpgradePlanDetail) error,
) (model.UserUpgradeStatusDetail, bool, error) {

	var (
		got           bool
		statusDetails model.UserUpgradeStatusDetail
	)
	for _, order := range upgradeOrder {
		var (
			fTs []model.FirmwareTypeDetail
			ft  model.FirmwareTypeDetail
		)

		_ = json.Unmarshal(order.Type, &fTs)
		if len(fTs) > 0 {
			ft = fTs[0]
		}
		if vv, ok := ftSettings[ft.Name]; !ok {
			continue
		} else {

			got = true
			statusDetail := model.UserUpgradeStatusDetailItem{
				FirmwareTypeName: order.Name,
				FirmwareId:       order.FirmwareId,
				DeviceType:       int(vv.DeviceProtocolCmd),
				Status:           int(model.UpgradeResultUpgrading),
			}

			if strings.HasPrefix(in.SNCode, "BSC22") {
				statusDetail.Progress = float32(model.UpgradeV2InitialProgressRatio)
				if fv2CallBack != nil {
					_ = fv2CallBack(uint64(in.ID), vv, order)
				}
			}
			if strings.HasPrefix(in.SNCode, "BSC21") {

				statusDetail.Progress = float32(model.UpgradeV1InitialProgressRatio)
				if fv1CallBack != nil {
					_ = fv1CallBack(ft, order)
				}
			}
			statusDetails = append(statusDetails, statusDetail)
		}
	}

	return statusDetails, got, nil
}

func (a *ota) upgradeV1(in model.OtaUpgradeRequest, currentVersion string,
	ft model.FirmwareTypeDetail, order model.UpgradePlanDetail) ([]string, []string, []string, error) {

	var (
		ctx, _               = context.WithTimeout(context.Background(), time.Minute*3)
		errMessage           []string
		infoMessage          []string
		noNeedUpgradeVersion []string
	)
	if strings.ToLower(order.Version) == fmt.Sprintf("v%v", currentVersion) {
		/* 如果要升级的版本和当前版本一致,那么将状态修改为成功 */
		noNeedUpgradeVersion = append(noNeedUpgradeVersion, fmt.Sprintf("ty:%v version:%+v", ft.Name, order.Version))
		return nil, nil, noNeedUpgradeVersion, nil
	}

	var (
		ossSettings     = config.Conf.OSS
		firmwarePath, _ = url.JoinPath(fmt.Sprintf("http://%v.oss-%v.aliyuncs.com", ossSettings.Bucket, ossSettings.Region), order.FilePath)
	)

	//推送设备，下发固件地址
	err := RpcCall(in.SNCode, func(addr string, err error) error {
		if err != nil {
			errMessage = append(errMessage, fmt.Sprintf("get signal address err %v", err))
			return err
		}

		upgradeRequest := &dto.UpgradeRequest{
			Sn:   in.SNCode,
			Addr: firmwarePath,
		}

		requestStr, _ := json.Marshal(upgradeRequest)
		_, err = grpc.Cmd.Upgrade(ctx, upgradeRequest, func(options *client.CallOptions) {
			options.Address = []string{addr}
		})
		if err != nil {
			errMessage = append(errMessage, fmt.Sprintf("push device:%v addr:%v err %v", in.SNCode, addr, err))
			return errors.DeviceCommandFailed.Error()
		}
		infoMessage = append(infoMessage, fmt.Sprintf("send upgrade cmd(%X) to address:%v file_path:%+v", requestStr, addr, firmwarePath))

		return err
	})

	return infoMessage, errMessage, noNeedUpgradeVersion, err
}

func (a *ota) Upgrade(ctx context.Context, in *model.OtaUpgradeRequest) error {

	var (
		err          error
		errMessage   []string
		infoMessage  []string
		upgradeCache *userUpgradeValueCache
	)

	defer func() {
		var (
			logger  = log.Info
			message = infoMessage
			result  = "SUCCESS"
		)
		if errMessage != nil {
			logger = log.Error
			result = "FAILED"
			message = append(message, errMessage...)
		}

		logger(fmt.Sprintf("【 Upgrade Request:%+v Result:%v 】 %v",
			in.String(), result, strings.Join(message, ",")))
	}()

	if in.Status == nil {
		errMsg := errors.ParameterErr
		errMessage = append(errMessage, errMsg.String())
		return errMsg.Error()
	}
	ctx, _ = context.WithTimeout(ctx, time.Minute*3)
	eBikeInfo, e := grpc.EBikeCmd.GetEbikeInfo(ctx, &manager.EbikeInfoReq{SnCode: &in.SNCode})
	if e != nil {
		errMsg := errors.InternalServerError
		errMessage = append(errMessage, fmt.Sprintf("request grpc service[manager] error:%v", e.Error()))
		return errMsg.Error()
	} else {
		if eBikeInfo.BikeInfo.BikeId <= 0 {
			errMsg := errors.EbikeNotExist
			errMessage = append(errMessage, errMsg.String())
			return errMsg.Error()
		}
	}

	upgradeCache, err = getUserUpgradeCache(ctx, in)
	if err != nil {
		errMessage = append(errMessage, fmt.Sprintf("get upgrade plan cache err %v", err))
		return err
	}

	var (
		statusErrMap = map[model.OtaUpgradeResultType]errors.Type{
			model.UpgradeResultUpgrading:   errors.OtaAlreadyUpgrading,
			model.UpgradeResultSuccess:     errors.OtaAlreadySuccess,
			model.UpgradeResultFailure:     errors.OtaAlreadyFailure,
			model.UpgradeResultAbolishment: errors.OtaAlreadyAbolishment,
		}
		s = model.OtaUpgradeResultType(upgradeCache.UserUpgradeStatus)
	)

	if s != model.UpgradeResultPending {
		errMessage = append(errMessage, fmt.Sprintf("upgrade status err:current status is %v", s))
		return statusErrMap[s].Error()
	}

	var (
		userUpgradeStatus = model.UpgradeResultUpgrading
		upgradePlanStatus = model.OtaUpgradePlanStatusType(upgradeCache.UpgradePlanStatus)
		allowStatus       = map[model.OtaUpgradePlanStatusType]struct{}{
			model.UpgradePlanStatusPending:   {},
			model.UpgradePlanStatusUpgrading: {},
		}
		upgradeOrder []model.UpgradePlanDetail
	)

	err = json.Unmarshal(upgradeCache.UpgradePlanDetail, &upgradeOrder)
	if err != nil {
		errMessage = append(errMessage, fmt.Sprintf("parse upgrade plan detail err %v", err))
		return err
	}

	if _, ok := allowStatus[upgradePlanStatus]; !ok {
		errMessage = append(errMessage, fmt.Sprintf("current upgrade plan status is %v,refuse to upgrade", upgradePlanStatus))
		return err
	}

	/*
	   1. 强制升级场景，用户只能升级
	   2. 自主升级,静默升级场景，用户可以取消或者选择升级,如果选择升级,如果升级状态不是待升级状态,那么返回错误
	*/

	userUpgradeStatus, err = a.upgradeCheck(upgradeCache, in)
	if err != nil {
		errMessage = append(errMessage, fmt.Sprintf("parse upgrade plan detail err %v", err))
		return err
	}

	var (
		got                  bool
		ftSettings           = config.Conf.FirmwareTypeSettings.ToMap1()
		noNeedUpgradeVersion []string
		currentVersion       string
	)

	if vk, e1 := grpc.SCmd.GetShadowAttributeKeys(ctx, &dto.GetShadowAttributeKeysRequest{
		DeviceName: in.SNCode,
		Key:        ShadowFirmwareVersionKey,
	}); e1 != nil {
		errMessage = append(errMessage, fmt.Sprintf("get current version errr:%+v", e1))
	} else {
		if vk == nil || len(vk.Value) == 0 {
			infoMessage = append(infoMessage, fmt.Sprintf("No current version information found"))
		} else {
			currentVersion = vk.Value
		}
	}

	var statusDetails model.UserUpgradeStatusDetail
	statusDetails, got, err = a.upgradeCallBack(ftSettings, in, upgradeOrder, nil, nil)
	if !got {
		errMessage = append(errMessage, fmt.Sprintf("cannot find firmware type info"))
		return errors.FirmwareTypeNotExist.Error()
	}
	if err = a.iUserUpgrade.ExecTransaction(ctx, func(ctx context.Context) error {
		if err = UserUpgrade.Update(ctx, &model.UserUpgradeUpdateRequest{
			Id:           &in.ID,
			Status:       utils.GetValuePointer(int(userUpgradeStatus)),
			Progress:     utils.GetValuePointer(statusDetails.CalcTotalProgress()),
			StatusDetail: statusDetails.Json(),
		}); err != nil {
			errMessage = append(errMessage, fmt.Sprintf("update user upgrade err %v", err))
			return err
		}

		if upgradePlanStatus != model.UpgradePlanStatusUpgrading {
			if err = UpgradePlan.Update(ctx, &model.UpgradePlanUpdateRequest{
				Id:     utils.GetValuePointer(in.PlanId),
				Status: utils.GetValuePointer(int(model.UpgradePlanStatusUpgrading)),
			}); err != nil {
				errMessage = append(errMessage, fmt.Sprintf("update upgrade plan err %v", err))
				return err
			}
		}

		return nil
	}); err != nil {
		errMessage = append(errMessage, fmt.Sprintf("update callback err:%+v", err.Error()))
		return errors.InternalServerError.Error()
	}
	var (
		fv2CallBack = func(id uint64, vv config.FirmwareTypeSettingsItem, order model.UpgradePlanDetail) error {
			fileLength := a.firmwareCache.getSize(id, order.FilePath)
			err = a.v2.doUpgrade0x01(ctx, id, in.SNCode, uint8(vv.DeviceProtocolCmd), fileLength)
			if err != nil {
				return err
			}
			return nil
		}
		fv1CallBack = func(ft model.FirmwareTypeDetail, order model.UpgradePlanDetail) error {

			if strings.ToLower(order.Version) == fmt.Sprintf("v%v", currentVersion) {
				/* 如果要升级的版本和当前版本一致,那么将状态修改为成功 */
				noNeedUpgradeVersion = append(noNeedUpgradeVersion, fmt.Sprintf("ty:%v version:%+v", ft.Name, order.Version))
				return nil
			}

			var (
				ossSettings     = config.Conf.OSS
				firmwarePath, _ = url.JoinPath(fmt.Sprintf("http://%v.oss-%v.aliyuncs.com", ossSettings.Bucket, ossSettings.Region), order.FilePath)
			)

			//推送设备，下发固件地址
			err = RpcCall(in.SNCode, func(addr string, err error) error {
				if err != nil {
					errMessage = append(errMessage, fmt.Sprintf("get signal address err %v", err))
					return err
				}

				upgradeRequest := &dto.UpgradeRequest{
					Sn:   in.SNCode,
					Addr: firmwarePath,
				}

				requestStr, _ := json.Marshal(upgradeRequest)
				_, err = grpc.Cmd.Upgrade(ctx, upgradeRequest, func(options *client.CallOptions) {
					options.Address = []string{addr}
				})
				if err != nil {
					errMessage = append(errMessage, fmt.Sprintf("push device:%v addr:%v err %v", in.SNCode, addr, err))
					return errors.DeviceCommandFailed.Error()
				}
				infoMessage = append(infoMessage, fmt.Sprintf("send upgrade cmd(%X) to address:%v file_path:%+v", requestStr, addr, firmwarePath))

				return err
			})

			return err
		}
	)

	statusDetails, got, err = a.upgradeCallBack(ftSettings, in, upgradeOrder, fv1CallBack, fv2CallBack)

	if len(noNeedUpgradeVersion) > 0 {
		infoMessage = append(infoMessage, fmt.Sprintf("no need upgrade current version:%v configured version:%+v", currentVersion, strings.Join(noNeedUpgradeVersion, ",")))
		_ = a.OTAUpgradeSuccess(&dto.OTAUpgradeNtf{Sn: in.SNCode}, currentVersion, model.UpgradeResultFromSelfLogic)
	}

	return err
}

/*
OTA 升级指令的回复报文,0x00
*/
func (a *ota) OTAUpgradeResponse(data *dto.UpgradeBSC22Data) (err error) {

	fMap := map[dto.BSC22UpgradeCmdType]func(*dto.UpgradeBSC22Data) (err error){
		dto.BSC22UpgradeCmdType_Cmd0x00: a.dealOTAUpgradeBSC220x00,
		dto.BSC22UpgradeCmdType_Cmd0x02: a.dealOTAUpgradeBSC220x02,
	}

	if _, ok := fMap[data.UpgradeCmd]; !ok {
		return fmt.Errorf("upgrade cmd not exist")
	}
	return fMap[data.UpgradeCmd](data)
}

func (a *ota) calcProgressV2(request *dto.UpgradeBSC22Data, actualLen int) float32 {

	/*
	   判断是从App1 下载还是从App2 下载
	*/
	var (
		startPos        float32
		endPos          float32
		halfOfActualLen = uint32(actualLen / 2)
	)
	if request.BindAddress >= halfOfActualLen {
		endPos = float32(request.BindLength + (request.BindAddress - halfOfActualLen))
	} else {
		endPos = float32(request.BindLength + (request.BindAddress - 0))
	}

	fmt.Println("当前进度:", (endPos-startPos)/float32(halfOfActualLen)*model.UpgradeV2DownLoadRatio+model.UpgradeV2InitialProgressRatio)
	return (endPos-startPos)/float32(halfOfActualLen)*model.UpgradeV2DownLoadRatio + model.UpgradeV2InitialProgressRatio
}

func (a *ota) dealOTAUpgradeBSC220x02(request *dto.UpgradeBSC22Data) (err error) {

	var (
		id                      = int64(request.Id)
		ctx                     = context.Background()
		fType                   = request.DeviceType
		upgradePlanDetail       []model.UpgradePlanDetail
		statusDetails           model.UserUpgradeStatusDetail
		firmwareTypeSettingsMap = config.Conf.FirmwareTypeSettings.ToMap()
		responseBody            []byte
	)

	upgradeCache, err := getUserUpgradeCache(ctx, &model.OtaUpgradeRequest{ID: id})
	if err != nil {
		return err
	}

	userUpgradeStatus := model.OtaUpgradeResultType(upgradeCache.UserUpgradeStatus)
	if userUpgradeStatus != model.UpgradeResultUpgrading {
		return fmt.Errorf("current upgrade status is %v,refuse to upgrade", userUpgradeStatus)
	}

	name, ok := firmwareTypeSettingsMap[uint16(fType)]
	if !ok {
		return fmt.Errorf("%v unsupport firmware type,config is error ", fType)
	}

	if err = json.Unmarshal(upgradeCache.UpgradePlanDetail, &upgradePlanDetail); err != nil {
		return err
	}
	if err = json.Unmarshal(upgradeCache.UserUpgradeStatusDetail, &statusDetails); err != nil {
		return err
	}

	fTypeIndex := statusDetails.Index(int(fType))
	var got bool
	for _, v := range upgradePlanDetail {

		if got {
			break
		}
		var types []model.FirmwareTypeDetail
		_ = json.Unmarshal(v.Type, &types)
		if len(types) == 0 {
			continue
		}

		if types[0].Name != name {
			continue
		}

		body := a.firmwareCache.getBody(uint64(v.FirmwareId), v.FilePath)
		actualLen := uint32(len(body))
		/* 越界判断 */
		if request.BindAddress >= actualLen || request.BindAddress+request.BindLength > actualLen {
			return fmt.Errorf("request bind_address:%v bind_length:%v out of range,actual is %v", request.BindAddress, request.BindLength, actualLen)
		}

		got = true
		/* 实际只取固件的一半 */
		statusDetails[fTypeIndex].Progress = a.calcProgressV2(request, int(actualLen))
		responseBody = make([]byte, request.BindLength)
		copy(responseBody, body[request.BindAddress:request.BindAddress+request.BindLength])
	}
	if !got {
		return fmt.Errorf("%v unsupport firmware type,config is error ", fType)
	}
	if err = UserUpgrade.Update(ctx, &model.UserUpgradeUpdateRequest{
		Id:           &id,
		Progress:     utils.GetValuePointer(statusDetails.CalcTotalProgress()),
		StatusDetail: statusDetails.Json(),
	}); err != nil {
		return fmt.Errorf("update user upgrade err %v", err)
	}

	return a.v2.dealUpgrade0x02(ctx, uint64(id), upgradeCache.EbikeSnCode, uint8(request.DeviceType), request.BindAddress, request.BindLength, responseBody)
}

func (a *ota) dealOTAUpgradeBSC220x00(response *dto.UpgradeBSC22Data) (err error) {

	var (
		id        = int64(response.Id)
		ctx, _    = context.WithTimeout(context.Background(), time.Minute*1)
		statusMap = map[dto.BSC22UpgradeResult]struct {
			model.OtaUpgradeResultType
			failureReason string
		}{
			dto.BSC22UpgradeResult_NACK: {
				OtaUpgradeResultType: model.UpgradeResultFailure,
				failureReason:        fmt.Sprintf("device response nack"),
			},
			dto.BSC22UpgradeResult_ACK: {
				OtaUpgradeResultType: model.UpgradeResultUpgrading,
			},
			dto.BSC22UpgradeResult_ERROR: {
				OtaUpgradeResultType: model.UpgradeResultFailure,
				failureReason:        fmt.Sprintf("device response error"),
			},
			dto.BSC22UpgradeResult_ERROR_COVER: {
				OtaUpgradeResultType: model.UpgradeResultFailure,
				failureReason:        fmt.Sprintf("device response error cover"),
			},
			dto.BSC22UpgradeResult_ERROR_PARAMETER: {
				OtaUpgradeResultType: model.UpgradeResultFailure,
				failureReason:        fmt.Sprintf("device response error parameter"),
			},
			dto.BSC22UpgradeResult_UPDATE_DOWNLOAD_FINISH: {
				OtaUpgradeResultType: model.UpgradeResultUpgrading,
			},
			dto.BSC22UpgradeResult_UPDATE_SUCCESS: {
				OtaUpgradeResultType: model.UpgradeResultSuccess,
			},
			dto.BSC22UpgradeResult_UPDATE_FAILED: {
				OtaUpgradeResultType: model.UpgradeResultFailure,
				failureReason:        fmt.Sprintf("device response error update failed"),
			},
			dto.BSC22UpgradeResult_UPDATE_ROLLBACK: {
				OtaUpgradeResultType: model.UpgradeResultFailure,
				failureReason:        fmt.Sprintf("device response error update rollback"),
			},
		}
		responseStatus          = dto.BSC22UpgradeResult(response.Body[0])
		firmwareTypeSettingsMap = config.Conf.FirmwareTypeSettings.ToMap()
		fType                   = response.DeviceType
		statusDetails           model.UserUpgradeStatusDetail
		infoMessage             []string
		errMessage              []string
	)

	defer func() {

		var (
			logger  = log.Info
			message = infoMessage
			result  = "SUCCESS"
		)
		if errMessage != nil {
			logger = log.Error
			result = "FAILED"
			message = append(message, errMessage...)
		}

		logger(fmt.Sprintf("【 dealOTAUpgradeBSC220x00 Request:(sn:%+v,cmd:%v,id:%d,device_type:%v,bind_length:%v,bind_address:%v,hex:%X) Result:%v 】 %v",
			response.Sn, response.UpgradeCmd, response.Id, response.DeviceType, response.BindLength, response.BindAddress, BuildPacket(response).ToHexBytes(), result, strings.Join(message, ",")))
	}()

	respUserUpgrade, err := getUserUpgradeCache(ctx, &model.OtaUpgradeRequest{
		ID: id,
	})
	if err != nil {
		return err
	}
	if _, ok := statusMap[responseStatus]; !ok {
		e := fmt.Errorf("unsupport response status %v", responseStatus)
		errMessage = append(errMessage, e.Error())
		return e
	}

	if model.OtaUpgradeResultType(respUserUpgrade.UserUpgradeStatus) != model.UpgradeResultUpgrading {
		e := fmt.Errorf("response status is %v while actual status is %v,refuse to upgrade", responseStatus, model.OtaUpgradeResultType(respUserUpgrade.UserUpgradeStatus))
		errMessage = append(errMessage, e.Error())
		return e
	}

	_, ok := firmwareTypeSettingsMap[uint16(fType)]
	if !ok {
		return fmt.Errorf("%v unsupport firmware type,config is error ", fType)
	}

	if err = json.Unmarshal(respUserUpgrade.UserUpgradeStatusDetail, &statusDetails); err != nil {
		return err
	}

	fTypeIndex := statusDetails.Index(int(fType))

	var (
		s    = statusMap[responseStatus]
		data = &model.UserUpgradeUpdateRequest{
			Id:     &id,
			Status: utils.GetValuePointer(int(s.OtaUpgradeResultType)),
			PlanId: utils.GetValuePointer(respUserUpgrade.PlanId),
		}
	)

	if s.failureReason != "" {
		t := time.Now().Unix()
		data.FailureReason = &s.failureReason
		data.FailureLatestTime = &t
		userUpgradeStatusCache.Add(respUserUpgrade.ID, model.UpgradeResultFailure)
	} else {
		/*
		   下载升级包占用整个升级过程中的 model.DownLoadRatio 的进度
		*/
		userUpgradeStatusCache.Add(respUserUpgrade.ID, model.UpgradeResultUpgrading)
		if dto.BSC22UpgradeResult(s.OtaUpgradeResultType) == dto.BSC22UpgradeResult_UPDATE_DOWNLOAD_FINISH {
			statusDetails[fTypeIndex].Progress = float32(model.UpgradeV2DownLoadRatio + model.UpgradeV2InitialProgressRatio)
			statusDetails[fTypeIndex].Status = int(model.UpgradeResultUpgrading)
			data.Progress = utils.GetValuePointer(statusDetails.CalcTotalProgress())
			data.StatusDetail = statusDetails.Json()
		}

		if dto.BSC22UpgradeResult(s.OtaUpgradeResultType) == dto.BSC22UpgradeResult_UPDATE_SUCCESS {
			statusDetails[fTypeIndex].Progress = float32(1.00)
			statusDetails[fTypeIndex].Status = int(model.UpgradeResultSuccess)
			totalProgress := statusDetails.CalcTotalProgress()
			data.Progress = utils.GetValuePointer(totalProgress)
			data.StatusDetail = statusDetails.Json()
			if totalProgress == float32(1) {
				userUpgradeStatusCache.Add(respUserUpgrade.ID, model.UpgradeResultSuccess)
			} else {
				data.Status = utils.GetValuePointer(int(model.UpgradeResultUpgrading))
			}
		}
	}

	infoMessage = append(infoMessage, fmt.Sprintf("update userupgarde's status %v", string(data.StatusDetail)))

	if infos, err := a.updateUserUpgrade(ctx, data); err != nil {
		e := fmt.Errorf("update user upgrade err %v", err)
		errMessage = append(errMessage, e.Error())
		return e
	} else {
		infoMessage = append(infoMessage, infos)
	}

	return a.v2.dealUpgrade0x00(ctx, uint64(id), response.Sn, uint8(response.DeviceType), response.BindAddress, response.BindLength, response.Body)
}

func (a *ota) updateUserUpgrade(ctx context.Context, data *model.UserUpgradeUpdateRequest) (string, error) {

	/* 不管是成功还是失败都会检测是否有排队中的任务要执行 */
	var infos []string
	if err := UserUpgrade.Update(ctx, data); err != nil {
		return strings.Join(infos, ","), fmt.Errorf("update user upgrade err %v", err)
	}

	if err := UpgradePlan.updateStatus(ctx, &model.UpgradePlanInfoRequest{Id: *data.PlanId}); err != nil {
		return "", fmt.Errorf("update upgrade plan status failed:%v", err.Error())
	} else {
		infos = append(infos, fmt.Sprintf("update upgrade plan status success"))
	}

	if id, err := Ota.UpgradeNext(ctx, *data.Id); err != nil {
		return strings.Join(infos, fmt.Sprintf("exec next upgrading err:%v", err.Error())), nil
	} else {
		infos = append(infos, fmt.Sprintf("sumbit next upgrading task(%v) about %v after %v", id, *data.EbikeId, *data.Id))
	}

	return strings.Join(infos, ","), nil
}

// ty:1 success ty:2 failed
func (a *ota) UpdateOTAResultV1(data *dto.OTAUpgradeNtf, version string, ty int, source model.UpgradeResultFrom) (err error) {
	var (
		errMessage      []string
		infoMessage     []string
		reason          = a.v1.getInfo(int(data.Result))
		userUpgradeId   int64
		respUserUpgrade *entity.UserUpgrade
		ctx             = context.Background()
	)

	defer func() {

		var (
			logger  = log.Info
			message = infoMessage
			result  = "SUCCESS"
		)
		if errMessage != nil {
			logger = log.Error
			result = "FAILED"
			message = append(message, errMessage...)
		}

		logger(fmt.Sprintf("【 UpdateOTAResultV1 Request:(sn:%+v,ty:%+v,source from:%+v,version:%+v result:%+v) id:%v Result:%v 】 %v",
			data.Sn, ty, source, version, data.Result, userUpgradeId, result, strings.Join(message, ",")))

	}()

	_, list, err := Ota.iUserUpgrade.List(ctx, &model.UserUpgradeListRequest{
		EbikeSnCode: &data.Sn,
		Status:      utils.GetValuePointer(int(model.UpgradeResultUpgrading)),
		Index:       1,
		Size:        1,
	}, model.QueryOptions{
		OrderFields: model.NewOrderFields(model.NewOrderField("created_at", model.OrderByASC)),
		OnlyList:    true,
	})
	if err != nil {
		errMessage = append(errMessage, fmt.Sprintf("get user upgrade list failed: %v", err))
		return err
	}

	if len(list) == 0 {
		e := fmt.Errorf("no user upgrade info found")
		errMessage = append(errMessage, e.Error())
		return e
	}

	respUserUpgrade = list[0]
	var (
		t            = time.Now().Unix()
		updateStatus = model.UpgradeResultSuccess
		updateData   = &model.UserUpgradeUpdateRequest{
			Id:       &respUserUpgrade.Id,
			Status:   utils.GetValuePointer(int(updateStatus)),
			EbikeId:  utils.GetValuePointer(respUserUpgrade.EbikeId),
			Progress: utils.GetValuePointer(float32(1.00)),
			PlanId:   utils.GetValuePointer(respUserUpgrade.PlanId),
		}
	)

	if ty == 2 {
		updateData.FailureReason = &reason
		updateData.FailureLatestTime = &t
		updateStatus = model.UpgradeResultFailure
		updateData.Status = utils.GetValuePointer(int(updateStatus))
	}

	if infos, err := a.updateUserUpgrade(ctx, updateData); err != nil {
		e := fmt.Errorf("update user upgrade err %v", err)
		errMessage = append(errMessage, e.Error())
		return e
	} else {
		infoMessage = append(infoMessage, infos)
	}

	infoMessage = append(infoMessage, fmt.Sprintf("user upgrade success"))
	return nil
}

func (a *ota) OTAUpgradeSuccess(data *dto.OTAUpgradeNtf, version string, source model.UpgradeResultFrom) (err error) {
	return a.UpdateOTAResultV1(data, version, 1, source)
}

func (a *ota) OTAUpgradeFailed(data *dto.OTAUpgradeNtf, source model.UpgradeResultFrom) (err error) {
	return a.UpdateOTAResultV1(data, "", 2, source)
}

/* 通过设备上报的版本信息判断是否有升级成功 */
func (a *ota) CheckUpgradeResult(data *dto.Controller) (err error) {

	s := int(model.UpgradeResultUpgrading)
	_, l, err := a.iUserUpgrade.List(context.Background(), &model.UserUpgradeListRequest{
		EbikeSnCode: &data.Sn,
		Status:      &s,
		Index:       1,
		Size:        1,
	}, model.QueryOptions{OnlyList: true, OrderFields: model.NewOrderFields(model.NewOrderField("id", model.OrderByASC))})
	if err != nil {
		return errors.Wrap(err, "list user upgrade fail")
	}

	if len(l) == 0 {
		return fmt.Errorf("no upgrading status upgrade result")
	}

	var (
		got             bool
		versions        []string
		userUpgradeData = l[0]
		jsonDetail      []model.UpgradePlanDetail
	)
	_ = json.Unmarshal(userUpgradeData.Detail, &jsonDetail)

	for _, v := range jsonDetail {
		if strings.ToLower(v.Version) == fmt.Sprintf("v%v", data.FirmwareVersion) {
			got = true
		}
		versions = append(versions, v.Version)
	}

	if !got {
		return fmt.Errorf("not match upgrading version:(%v) report version: %v", strings.Join(versions, ","), data.FirmwareVersion)
	}

	return a.OTAUpgradeSuccess(&dto.OTAUpgradeNtf{Sn: data.Sn}, data.FirmwareVersion, model.UpgradeResultFromDeviceReport)
}
